﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Xml.Linq;
using MathematicallySafe.HeadCounter.Data.Common;

namespace MathematicallySafe.HeadCounter.ViewModels
{
    // HeadCount data only exists at import time.  So HC view models have no direct models
    // and are instead converted to their equivalent domain models when the import is performed.

    public class HeadCountRaidViewModel : ViewModelBase
    {
        private DateTime _raidDate;
        private string _zone;
        private RaidDifficulty _difficulty;
        private bool _optional;
        private bool _isValid;
        private string _validationMessage;

        public string Zone
        {
            get { return _zone; }
            private set
            {
                _zone = value;
                OnPropertyChanged("Zone");
            }
        }

        public RaidDifficulty Difficulty
        {
            get { return _difficulty; }
            set
            {
                _difficulty = value;
                OnPropertyChanged("Difficulty");
            }
        }

        public bool Optional
        {
            get { return _optional; }
            set
            {
                _optional = value;
                OnPropertyChanged("Optional");
            }
        }

        public string Description
        {
            get { return Zone + " (" + Difficulty + ") " + DisplayDate; }
        }

        public bool IsValid
        {
            get { return _isValid; }
            set
            {
                _isValid = value;
                OnPropertyChanged("IsValid");
            }
        }

        public string ValidationMessage
        {
            get { return _validationMessage; }
            set
            {
                _validationMessage = value;
                OnPropertyChanged("ValidationMessage");
            }
        }

        public DateTime RaidDate
        {
            get { return _raidDate; }

            private set
            {
                _raidDate = value;

                OnPropertyChanged("RaidDate");
                OnPropertyChanged("DisplayDate");
            }
        }

        public string DisplayDate
        {
            get { return _raidDate.Date.ToShortDateString(); }
        }

        public ObservableCollection<HeadCountRaiderViewModel> Raiders { get; private set; }
        public ObservableCollection<HeadCountLootViewModel> Loot { get; private set; }

        public void Load(XElement raidXml)
        {
            Zone = (string) raidXml.Element("zone");
            RaidDate = DateTime.Parse((string) raidXml.Element("start"));
            Difficulty = ParseDifficulty((string) raidXml.Element("difficulty"));

            LoadRaiders(raidXml);
            LoadLoot(raidXml);
        }

        private void LoadLoot(XElement raidXml)
        {
            Loot = new ObservableCollection<HeadCountLootViewModel>();

            IOrderedEnumerable<HeadCountLootViewModel> loot =
                from l in
                from lootElement in raidXml.Elements("loot").Elements("item")
                select new HeadCountLootViewModel
                    {
                       Id = (int) lootElement.Element("id"),
                       Name = (string) lootElement.Element("name"),
                       LooterName = (string) lootElement.Element("looter"),
                       Boss = (string) lootElement.Element("source"),
                       Time = DateTime.Parse((string) lootElement.Element("time")),
                       Cost = (int) lootElement.Element("cost"),
                       LootType = (string) lootElement.Element("type"),
                       LootSubType = (string) lootElement.Element("subType"),
                    }
                orderby l.Time , l.Boss , l.Name
                select l;

            loot.ForEach(l => Loot.Add(l));

            CheckForInvalidLoot(loot);
        }

        private void CheckForInvalidLoot(IOrderedEnumerable<HeadCountLootViewModel> loot)
        {
            var unsafeItem = loot.FirstOrDefault(l => l.Name == "Item unavailable");
            if (unsafeItem != null)
            {
                ValidationMessage = "You have at least one 'Item unavailable' in your HeadCount data. Fix in-game and re-export.";
                IsValid = false;
            }
            else
            {
                ValidationMessage = null;
                IsValid = true;
            }
        }

        private void LoadRaiders(XContainer raidXml)
        {
            Raiders = new ObservableCollection<HeadCountRaiderViewModel>();

            IEnumerable<HeadCountRaiderViewModel> raiders =
                from playerElement in raidXml.Elements("players").Elements("player")
                select new HeadCountRaiderViewModel
                       {
                           Name = (string) playerElement.Element("name"),
                           Class = ParseClass((string) playerElement.Element("class")),
                           Race = ParseRace((string) playerElement.Element("race")),
                           TotalTime = new TimeSpan(0, 0, (int) playerElement.Element("raidDuration")),
                           WaitTime = new TimeSpan(0, 0, (int) playerElement.Element("waitDuration")),
                           Offline = new TimeSpan(0, 0, (int) playerElement.Element("offlineDuration"))
                       };

            raiders.ForEach(r =>
            {
                Raiders.Add(r);
                r.PropertyChanged += RaiderPropertyChanged;
            });
        }

        void RaiderPropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
        {
            if (e.PropertyName == "Ignore")
            {
                var raider = sender as HeadCountRaiderViewModel;

                if (raider != null)
                {
                    IEnumerable<HeadCountLootViewModel> ignoredLoot = from r in Raiders
                                                                      where r.Name == raider.Name
                                                                      join l in Loot on r.Name equals l.LooterName
                                                                      select l;

                    ignoredLoot.ForEach(l => l.Ignore = raider.Ignore);
                }
            }
        }


        #region Helpers

        private CharacterClass ParseClass(string classText)
        {
            if (string.IsNullOrEmpty(classText)) return CharacterClass.Hunter;
            if (classText == "Death Knight") return CharacterClass.Deathknight;
            return (CharacterClass) Enum.Parse(typeof (CharacterClass), classText);
        }

        private CharacterRace ParseRace(string raceText)
        {
            if (string.IsNullOrEmpty(raceText)) return CharacterRace.Human;
            return (CharacterRace) Enum.Parse(typeof (CharacterRace), raceText);
        }

        private RaidDifficulty ParseDifficulty(string difficultyText)
        {
            if (string.IsNullOrEmpty(difficultyText))
            {
                return RaidDifficulty.Normal25;
            }

            if (difficultyText == "10 Player") return RaidDifficulty.Normal10;
            if (difficultyText == "25 Player") return RaidDifficulty.Normal25;

            return RaidDifficulty.Heroic25;
        }

        #endregion

        protected override void UnsubscribeEvents()
        {
            Raiders.ForEach(r => { r.PropertyChanged -= RaiderPropertyChanged; });
        }
    }
}